package device

import (
	"encoding/binary"
	"errors"
	"fmt"
	"reflect"
	"sync"
	"time"

	"github.com/alecthomas/log4go"
	"github.com/golang/protobuf/proto"
	"pac/client"
	"pac/conf"
	database "pac/db"
	pb "pac/proto"
)

type measure struct {
	MeasureRaw []byte

	// Three-phase voltage
	TPU float32
	// Three-phase current
	TPI float32
	// Three-phase frequency
	TPF float32
	// Three-phase active
	TPA float32
	// Three-phase reactive
	TPR float32
	// Three-phase power factor
	TPPF float32

	// Phase A voltage voltage
	PAU float32
	// Phase A current
	PAI float32
	// Phase A frequency
	PAF float32
	// Phase A active
	PAA float32
	// Phase A reactive
	PAR float32
	// Phase A power factor
	PAPF float32

	// Phase B voltage voltage
	PBU float32
	// Phase B current
	PBI float32
	// Phase B frequency
	PBF float32
	// Phase B active
	PBA float32
	// Phase B reactive
	PBR float32
	// Phase B power factor
	PBPF float32

	// Phase C voltage voltage
	PCU float32
	// Phase C current
	PCI float32
	// Phase C frequency
	PCF float32
	// Phase C active
	PCA float32
	// Phase C reactive
	PCR float32
	// Phase C power factor
	PCPF float32

	// Three-phase apparent power
	TPAP float32
	// TPhase A apparent power
	PAAP float32
	// Phase B apparent power
	PBAP float32
	// Phase C apparent power
	PCAP float32

	// Three-phase active energy
	TPAE uint32
	// Three-phase reactive energy
	TPRE uint32
	// Three-phase apparent energy
	TPAPPE uint32
}

type wave struct {
	WaveRaw []byte
	URaw    []byte
	U       []int16
	IRaw    []byte
	I       []int16
}

type reportMeasureReq struct {
	reqPkt

	measure
}

type reportMeasureRsp struct {
	rspPkt
}

type reportWaveReq struct {
	reqPkt

	wave
}

type reportWaveRsp struct {
	rspPkt
}

type reportInfoReq struct {
	reqPkt
	measure
	wave
}

type reportInfoRsp struct {
	rspPkt
}

func init() {
	cmdPktMap[CmdReportMeasureReq.String()] = reflect.TypeOf((*reportMeasureReq)(nil))
	cmdPktMap[CmdReportMeasureRsp.String()] = reflect.TypeOf((*reportMeasureRsp)(nil))
	cmdPktMap[CmdReportWaveReq.String()] = reflect.TypeOf((*reportWaveReq)(nil))
	cmdPktMap[CmdReportWaveRsp.String()] = reflect.TypeOf((*reportWaveRsp)(nil))
	cmdPktMap[CmdReportInfoReq.String()] = reflect.TypeOf((*reportInfoReq)(nil))
	cmdPktMap[CmdReportInfoRsp.String()] = reflect.TypeOf((*reportInfoRsp)(nil))
}

func (p *reportMeasureReq) String() string {
	return fmt.Sprintf("%s: Voltage: %.2f, Current: %.2f, Active: %.2f, Reactive: %.2f, electricity: %d. %v", p.CmdStr(), p.TPU, p.TPI, p.TPA, p.TPR, p.TPAE /*FIXME:*/, p.measure)
}

func (p *reportWaveReq) String() string {
	return fmt.Sprintf("%s: U: %d, U: %d", p.CmdStr(), len(p.U), len(p.I))
}

func (p *reportInfoReq) String() string {
	return fmt.Sprintf("%s: Voltage: %.2f, Current: %.2f, Active: %.2f, Reactive: %.2f, electricity: %d, U: %d, U: %d", p.CmdStr(), p.TPU, p.TPI, p.TPA, p.TPR, p.TPAE /*FIXME:*/, len(p.U), len(p.I))
}

func (m *measure) parseParam(raw []byte) {
	m.MeasureRaw = raw[0:68]

	// Three-phase voltage
	m.TPU = float32(binary.LittleEndian.Uint16(m.MeasureRaw[0:])) / 100
	// Three-phase current
	m.TPI = float32(binary.LittleEndian.Uint16(m.MeasureRaw[2:])) / 100
	// Three-phase frequency
	m.TPF = float32(binary.LittleEndian.Uint16(m.MeasureRaw[4:])) / 100
	// Three-phase active
	m.TPA = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[6:]))) / 100
	// Three-phase reactive
	m.TPR = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[8:]))) / 100
	// Three-phase power factor
	m.TPPF = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[10:]))) / 100

	// Phase A voltage voltage
	m.PAU = float32(binary.LittleEndian.Uint16(m.MeasureRaw[12:])) / 100
	// Phase A current
	m.PAI = float32(binary.LittleEndian.Uint16(m.MeasureRaw[14:])) / 100
	// Phase A frequency
	m.PAF = float32(binary.LittleEndian.Uint16(m.MeasureRaw[16:])) / 100
	// Phase A active
	m.PAA = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[18:]))) / 100
	// Phase A reactive
	m.PAR = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[20:]))) / 100
	// Phase A power factor
	m.PAPF = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[22:]))) / 100

	// Phase B voltage voltage
	m.PBU = float32(binary.LittleEndian.Uint16(m.MeasureRaw[24:])) / 100
	// Phase B current
	m.PBI = float32(binary.LittleEndian.Uint16(m.MeasureRaw[26:])) / 100
	// Phase B frequency
	m.PBF = float32(binary.LittleEndian.Uint16(m.MeasureRaw[28:])) / 100
	// Phase B active
	m.PBA = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[30:]))) / 100
	// Phase B reactive
	m.PBR = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[32:]))) / 100
	// Phase B power factor
	m.PBPF = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[34:]))) / 100

	// Phase C voltage voltage
	m.PCU = float32(binary.LittleEndian.Uint16(m.MeasureRaw[36:])) / 100
	// Phase C current
	m.PCI = float32(binary.LittleEndian.Uint16(m.MeasureRaw[38:])) / 100
	// Phase C frequency
	m.PCF = float32(binary.LittleEndian.Uint16(m.MeasureRaw[40:])) / 100
	// Phase C active
	m.PCA = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[42:]))) / 100
	// Phase C reactive
	m.PCR = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[44:]))) / 100
	// Phase C power factor
	m.PCPF = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[46:]))) / 100

	// Three-phase apparent power
	m.TPAP = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[48:]))) / 100
	// Phase A apparent power
	m.PAAP = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[50:]))) / 100
	// Phase B apparent power
	m.PBAP = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[52:]))) / 100
	// Phase C apparent power
	m.PCAP = float32(int16(binary.LittleEndian.Uint16(m.MeasureRaw[54:]))) / 100

	// Three-phase active energy
	m.TPAE = uint32(binary.LittleEndian.Uint16(m.MeasureRaw[56:])) | (uint32(binary.LittleEndian.Uint16(m.MeasureRaw[58:])) << 16)
	// Three-phase reactive energy
	m.TPRE = uint32(binary.LittleEndian.Uint16(m.MeasureRaw[60:])) | (uint32(binary.LittleEndian.Uint16(m.MeasureRaw[62:])) << 16)
	// Three-phase apparent energy
	m.TPAPPE = uint32(binary.LittleEndian.Uint16(m.MeasureRaw[64:])) | (uint32(binary.LittleEndian.Uint16(m.MeasureRaw[66:])) << 16)
}

func (p *reportMeasureReq) parseParam() error {
	if len(p.Payload) < 68 {
		return errors.New("reportMeasureReq.parseParam: invalid param")
	}

	p.measure.parseParam(p.Payload)

	return nil
}

func (w *wave) parseParam(raw []byte) {
	w.WaveRaw = raw[0:1024]

	w.URaw = make([]byte, 512)
	w.IRaw = make([]byte, 512)

	copy(w.URaw, raw[0:512])
	copy(w.IRaw, raw[512:1024])

	for i := 0; i < len(w.URaw); i += 2 {
		w.U = append(w.U, int16(binary.LittleEndian.Uint16(w.URaw[i:])))
	}

	for i := 0; i < len(w.IRaw); i += 2 {
		w.I = append(w.I, int16(binary.LittleEndian.Uint16(w.IRaw[i:])))
	}
}

func (p *reportWaveReq) parseParam() error {
	if len(p.Payload) < 1024 {
		return errors.New("reportWaveReq.parseParam: invalid param")
	}

	p.wave.parseParam(p.Payload)

	return nil
}

func (p *reportInfoReq) parseParam() error {
	if len(p.Payload) < 68+1024 {
		return errors.New("reportInfoReq.parseParam: invalid param")
	}

	p.measure.parseParam(p.Payload)
	p.wave.parseParam(p.Payload[68:])

	return nil
}

func (p *reportMeasureRsp) parseParam() error {
	return nil
}

func (p *reportWaveRsp) parseParam() error {
	return nil
}

func (p *reportInfoRsp) parseParam() error {
	return nil
}

func (p *reportMeasureReq) adjustCmd() {
	if p != nil {
		p.Cmd = CmdReportMeasureReq
	}
}

func (p *reportWaveReq) adjustCmd() {
	if p != nil {
		p.Cmd = CmdReportWaveReq
	}
}

func (p *reportInfoReq) adjustCmd() {
	if p != nil {
		p.Cmd = CmdReportInfoReq
	}
}

func (p *reportMeasureRsp) adjustCmd() {
	if p != nil {
		p.Cmd = CmdReportMeasureRsp
	}
}

func (p *reportWaveRsp) adjustCmd() {
	if p != nil {
		p.Cmd = CmdReportWaveRsp
	}
}

func (p *reportInfoRsp) adjustCmd() {
	if p != nil {
		p.Cmd = CmdReportInfoRsp
	}
}

func (p *reportMeasureReq) marshalParams() ([]byte, error) {
	return nil, nil
}

func (p *reportWaveReq) marshalParams() ([]byte, error) {
	return nil, nil
}

func (p *reportInfoReq) marshalParams() ([]byte, error) {
	return nil, nil
}

func (p *reportMeasureRsp) marshalParams() ([]byte, error) {
	return p.rspPkt.marshalParams()
}

func (p *reportWaveRsp) marshalParams() ([]byte, error) {
	return p.rspPkt.marshalParams()
}

func (p *reportInfoRsp) marshalParams() ([]byte, error) {
	return p.rspPkt.marshalParams()
}

// Marshal 编码
func (p *reportMeasureReq) Marshal() ([]byte, error) {
	return Marshal(p)
}

// Marshal 编码
func (p *reportWaveReq) Marshal() ([]byte, error) {
	return Marshal(p)
}

// Marshal 编码
func (p *reportInfoReq) Marshal() ([]byte, error) {
	return Marshal(p)
}

// Marshal 编码
func (p *reportMeasureRsp) Marshal() ([]byte, error) {
	return Marshal(p)
}

// Marshal 编码
func (p *reportWaveRsp) Marshal() ([]byte, error) {
	return Marshal(p)
}

// Marshal 编码
func (p *reportInfoRsp) Marshal() ([]byte, error) {
	return Marshal(p)
}

// Process 处理
func (p *reportMeasureReq) Process(cli *Client) error {
	processReportMeasure(cli, p)
	return nil
}

// Process 处理
func (p *reportWaveReq) Process(cli *Client) error {
	processReportWave(cli, p)
	return nil
}

// Process 处理
func (p *reportInfoReq) Process(cli *Client) error {
	processReportInfo(cli, p)
	return nil
}

// Process 处理
func (p *reportMeasureRsp) Process(cli *Client) error {
	return nil
}

// Process 处理
func (p *reportWaveRsp) Process(cli *Client) error {
	return nil
}

// Process 处理
func (p *reportInfoRsp) Process(cli *Client) error {
	return nil
}

func processReportMeasure(cli *Client, req *reportMeasureReq) {
	rsp := &reportMeasureRsp{}
	rsp.fromReq(req)

	if conf.DBAvailable {
		db := database.GetDb()
		_, err := db.Exec("INSERT INTO device_measure(device_id,datetime,voltage,current,active_power,reactive_power,electricity) VALUES(?,NOW(),?,?,?,?,?)", cli.GetID(), req.TPU, req.TPI, req.TPA, req.TPR, req.TPAE /*FIXME:*/)
		if err != nil {
			log4go.Warn(err)
			return
		}
	}

	notifMeasureToUser(cli.DeviceID, req.TPU, req.TPI, req.TPA, req.TPR, req.TPAE /*FIXME:*/)

	err := cli.SendPacket(rsp)
	if err != nil {
		log4go.Warn("SendPacket failure: %v", err)
	}
}

func processReportWave(cli *Client, req *reportWaveReq) {
	rsp := &reportWaveRsp{}
	rsp.fromReq(req)

	if conf.DBAvailable {
		db := database.GetDb()
		_, err := db.Exec("INSERT INTO device_wave(device_id,datetime,wave_raw,wave_voltage,wave_current) VALUES(?,NOW(),?,?,?)", cli.GetID(), req.WaveRaw, req.URaw, req.IRaw)
		if err != nil {
			log4go.Warn(err)
			return
		}
	}

	notifWaveToUser(cli.DeviceID, req.U, req.I)

	err := cli.SendPacket(rsp)
	if err != nil {
		log4go.Warn("SendPacket failure: %v", err)
	}
}

func processReportInfo(cli *Client, req *reportInfoReq) {
	rsp := &reportInfoRsp{}
	rsp.fromReq(req)

	if conf.DBAvailable {
		now := time.Now()

		db := database.GetDb()
		_, err := db.Exec("INSERT INTO device_measure(device_id,datetime,voltage,current,active_power,reactive_power,electricity) VALUES(?,?,?,?,?,?,?)", cli.GetID(), now, req.TPU, req.TPI, req.TPA, req.TPR, req.TPAE /*FIXME:*/)
		if err != nil {
			log4go.Warn(err)
			return
		}
		_, err = db.Exec("INSERT INTO device_wave(device_id,datetime,wave_raw,wave_voltage,wave_current) VALUES(?,?,?,?,?)", cli.GetID(), now, req.WaveRaw, req.URaw, req.IRaw)
		if err != nil {
			log4go.Warn(err)
			return
		}
	}

	notifMeasureToUser(cli.DeviceID, req.TPU, req.TPI, req.TPA, req.TPR, req.TPAE /*FIXME:*/)
	notifWaveToUser(cli.DeviceID, req.U, req.I)

	err := cli.SendPacket(rsp)
	if err != nil {
		log4go.Warn("SendPacket failure: %v", err)
	}
}

var notifToUserProtoMessageChan chan proto.Message
var notifToUserProtoMessageOnce sync.Once

func notifToUserProtoMessageInit() {
	notifToUserProtoMessageChan = make(chan proto.Message, 1024*4)
	go notifToUserProtoMessageWorker()
}

func notifMeasureToUser(deviceID string, voltage, current, activePower, reactivePower float32, electricEnergy uint32) {
	notifToUserProtoMessageOnce.Do(notifToUserProtoMessageInit)

	notifToUserProtoMessageChan <- &pb.ReportMeasureReq{
		DeviceId: deviceID,
		MeasureData: &pb.MeasureData{
			Timestamp:      time.Now().Unix(),
			Voltage:        float64(voltage),
			Current:        float64(current),
			ActivePower:    float64(activePower),
			ReactivePower:  float64(reactivePower),
			ElectricEnergy: float64(electricEnergy),
		},
	}
}

func notifWaveToUser(deviceID string, U, I []int16) {
	voltage := make([]float32, len(U))
	current := make([]float32, len(I))
	for i, v := range U {
		voltage[i] = float32(v)
	}
	for i, c := range I {
		current[i] = float32(c)
	}

	notifToUserProtoMessageOnce.Do(notifToUserProtoMessageInit)

	notifToUserProtoMessageChan <- &pb.ReportWaveReq{
		DeviceId: deviceID,
		WaveData: &pb.WaveData{
			Timestamp: time.Now().Unix(),
			Voltage:   voltage,
			Current:   current,
		},
	}
}

func notifToUserProtoMessageWorker() {
	for {
		select {
		case msg := <-notifToUserProtoMessageChan:
			client.NotifToUser(msg)
		}
	}
}
